import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { AbstractDragLimit } from "./abstract-drag-limit";
import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
import { IDragEventData, IDragProgressEventData } from "../config/abstract-drag-config";
import { Calculation } from "../../tool/calculation";
import { Camera } from '@babylonjs/core/Cameras/camera';

/**
 * 拖动中的回调函数
 * @param target 对象
 * @param manual 是否手动
 * @param t 控制参数
 */
interface IProgressCallback {
    (target: TransformNode, manual: boolean, t: number, eventData?: IDragEventData | IDragProgressEventData): void
}

/**
 * 约束在给定两点所代表的直线上的拖动
 * @param startPoint 起始点
 * @param endPoint 终点
 */
class DragLineLimit extends AbstractDragLimit {

    private _startPoint: Vector3 = new Vector3();
    private _endPoint: Vector3 = new Vector3();
    private _t: number = Number.POSITIVE_INFINITY;
    private _dir: Vector3 = new Vector3();
    private _pos: Vector3 = new Vector3();
    private _tV0: Vector3 = new Vector3();
    private _tV1: Vector3 = new Vector3();
    private _tV2: Vector3 = new Vector3();
    private _tV3: Vector3 = new Vector3();
    private _length: number = 0;

    constructor(startPoint: Vector3, endPoint: Vector3) {
        super();
        this.setInformation(startPoint, endPoint);
    }

    /**
     * 获取约束类型标识
     */
    public get limitType(): string {
        return 'LineLimit';
    }

    /**
     * 控制参数（起点到终点之间为0-1，小于零在起点端的外侧，大于1在终点端的外侧）
     */
    public get t(): number {
        return this._t;
    }

    /**
     * 直线上的两点
     * @param lineStart 起点
     * @param lineEnd 终点
     */
    public setInformation(lineStart: Vector3, lineEnd: Vector3): void {
        this._startPoint.copyFrom(lineStart);
        this._endPoint.copyFrom(lineEnd);
        this._length = Vector3.Distance(this._startPoint, this._endPoint);
    }

    /**
     * 拖动中的回调函数
     * @param func (target:目标;manual:手动触发;t:控制参数):void
     */
    public set onUpdate(func: IProgressCallback) {
        this.updateCall = func;
    }

    /**
     * 用给定控制参数更新目标
     * @param target 目标
     * @param t 控制参数（起点到终点之间为0-1，小于零在起点端的外侧，大于1在终点端的外侧）
     */
    public update(target: TransformNode, t: number): void {
        if (this._t === t) return;

        this._t = t;
        const start = this._startPoint;
        const end = this._endPoint;
        const dir = this._dir.copyFrom(end).subtractInPlace(start);
        dir.scaleInPlace(this._t);
        this.currentPosition.copyFrom(start).addInPlace(dir);
        this.applyPositionToTarget(target);
        if (this.updateCall) this.updateCall(target, false, this._t);
    }

    /**
     * 提取控制参数
     * @returns 控制参数
     */
    public extract(): number {
        return this._t;
    }

    public setStart(target: TransformNode, eventData: IDragEventData): void {
        super.setStart(target, eventData);
        const camera = this.getActiveCamera(target.getScene());
        if (!camera) return;
        const closest = this.computeClosestPoint(camera);
        this.currentPosition.copyFrom(closest);
        this.startOffset.copyFrom(closest).subtractInPlace(target.absolutePosition);

    }

    public setProgress(target: TransformNode, eventData: IDragProgressEventData): void {
        super.setProgress(target, eventData);
        const camera = this.getActiveCamera(target.getScene());
        if (!camera) return;
        const closest = this.computeClosestPoint(camera);
        this.currentPosition.copyFrom(closest);

        const start = this._startPoint;
        const end = this._endPoint;
        const twp = target.absolutePosition;
        const st = this._tV2.copyFrom(twp).subtractInPlace(start);
        const se = this._tV3.copyFrom(end).subtractInPlace(start);
        const sign = Vector3.Dot(st, se) > 0 ? 1 : -1;
        const len = Vector3.Distance(target.absolutePosition, this._startPoint);
        this._t = len / this._length * sign;
        if (this.updateCall) this.updateCall(target, true, this._t, eventData);
    }

    /**
     * 计算最近点
     * @param camera 当前的活动相机
     * @returns 最近点
     */
    private computeClosestPoint(camera: Camera): Vector3 {
        const start = this._startPoint;
        const end = this._endPoint;
        const cur = this.currentPosition;
        const cam = this._pos.copyFrom(camera.globalPosition);
        const closest1 = this._tV0;
        const closest2 = this._tV1;
        Calculation.closestPointsOfTwoLines(start, end, cam, cur, closest1, closest2);
        return closest1;
    }

}


export { DragLineLimit };